﻿#include <iostream>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/ip.h>	//ip 协议的头文件
#include <netinet/udp.h>	//udp 协议的头文件
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include "AAAAttrbute.h"
#include "AAAPack.h"

enum
{
    UDP_HEAD_LEN = 8,
    PSEUDO_LEN = 12
};


#pragma pack( push )
#pragma pack(1)
/**
 * @brief The pseudohdr struct   伪 ip 包头
 */
struct pseudohdr
{
    uint32_t srcIp;
    uint32_t dstIp;
    uint8_t zero;
    uint8_t  protocal;
    uint16_t len;
};
#pragma pack( pop )


 /* 校验算法
  1、把原校验和字段置为0；
  2、对IP头部中的每16bit进行二进制求和；
  3、如果和的高16bit不为0，则将和的高16bit和低16bit反复相加，直到和的高16bit为0，从而获得一个16bit的值；
  4、将该16bit的值取反，存入校验和字段。
  */
uint16_t _checksum(uint16_t *buffer,int size)
{
    unsigned long cksum = 0;
    while(size>1)
    {
        cksum += *buffer++;
        size -= sizeof(uint16_t);
    }
    if (size)
    {
        cksum += *(uint8_t *)buffer;
    }
    //将32位数转换成16
    while (cksum>>16)
        cksum=(cksum >> 16) + (cksum & 0xffff);

    return (uint16_t) (~cksum);
}


/*
 * 为 udp 定制的校验算法
 */
uint16_t _udpChecksum( const struct iphdr* ip,const struct udphdr* udp , const void* data,uint16_t dataLen )
{
    ///////////////////////// 填充虚头部 ///////////////////////////////////
    //总长度=虚部长度 + udp 长度
    uint16_t totalLen = UDP_HEAD_LEN + PSEUDO_LEN + dataLen;
    uint8_t *buf = new uint8_t[totalLen];
    struct pseudohdr* pseudo = (struct pseudohdr*)buf;
    pseudo->dstIp = ip->daddr;
    pseudo->srcIp = ip->saddr;
    pseudo->zero = 0;
    pseudo->protocal = IPPROTO_UDP;
    pseudo->len = udp->len;
    //增加 udp 数据包头进行校验
    memcpy( buf+PSEUDO_LEN, udp, UDP_HEAD_LEN );
    //增加数据到要校验的包
    memcpy( buf+PSEUDO_LEN + UDP_HEAD_LEN, data,dataLen );
    //进行校验
    uint16_t sum = _checksum( (uint16_t *)buf,totalLen );

    delete[]buf;
    return sum;
}


class RawSocketError : public std::exception
{
public:
    RawSocketError( const char* str ):
        _msg( str )
    {
    }
    virtual const char* what() const noexcept
    {
        return _msg.c_str();
    }
private:
    std::string _msg;
};

/**
 * @brief ConstructFakeIpLayer 构造 ip 层数据
 * @param srcIp [in] 要使用的源地址
 * @param dstIp [in] 要使用的目的地址
 * @param buf [out] 存放网络数据包的地址
 * @param totalLen buf 的长度,也是整个网络包的总长度
 * @warning invalid_argument
 */
void ConstructFakeIpLayer(const std::string& srcIp,
                              const std::string& dstIp,uint8_t* buf,uint16_t totalLen )
{
    // 参数检查
    if ( totalLen < sizeof( struct iphdr ) )
    {
        throw std::invalid_argument( "buf is to small for ip message" );
    }
    if( nullptr == buf )
    {
        throw std::invalid_argument( "buf is nullptr" );
    }
    struct in_addr saddr,daddr;
    if ( !inet_aton( srcIp.c_str(), &saddr) )
    {
        throw std::invalid_argument( "srcIp is not a ip" );
    }
    if ( !inet_aton( dstIp.c_str(), &daddr) )
    {
        throw std::invalid_argument( "dstIp is not a ip" );
    }

    ///////////////////////////　填充 ip 字段   /////////////////////////////
    struct iphdr* ip;
    ip = ( struct iphdr* )buf;
    ip->version = IPVERSION;
    ip->ihl = sizeof( struct ip ) >> 2;
    ip->tos = 0;
    ip->tot_len = htons( totalLen );
    ip->id = 0;
    ip->frag_off = 0;
    ip->ttl = MAXTTL;
    ip->protocol = IPPROTO_UDP;
    //这里首先设置为 0
    ip->check = 0;
    ip->daddr = daddr.s_addr;
    ip->saddr = saddr.s_addr;
    ip->check = _checksum( (uint16_t*)ip,  ip->ihl );
}

/**
 * @brief ConstructFakeUdpLayer 构造假的 udp 信息
 * @param ip
 * @param srcPort
 * @param dstPort
 * @param userData
 * @param dataLen
 * @param buf
 * @param totalLen buf 的总长度，不小于  totalLen < sizeof( struct iphdr ) + sizeof( struct udphdr )+dataLen
 */
void ConstructFakeUdpLayer( struct iphdr* ip, uint16_t srcPort,  uint16_t dstPort,
                            const void* userData,uint16_t dataLen,uint8_t* buf, uint16_t totalLen )
{
    // 参数检查
    if ( totalLen < sizeof( struct iphdr ) + sizeof( struct udphdr )+dataLen )
    {
        throw std::invalid_argument( "buf is to small for ip + udp message" );
    }
    if( nullptr == buf )
    {
        throw std::invalid_argument( "buf is nullptr" );
    }

     /////////////////////////   填充 udp 字段   ///////////////////////////////
    struct udphdr* udp;

    udp = ( struct 	udphdr* )( buf + sizeof( struct ip ) );
    udp->source = htons( srcPort );
    udp->dest = htons( dstPort );
    udp->len = htons( UDP_HEAD_LEN + dataLen );
    udp->check = 0;
    udp->check = _udpChecksum( ip, udp ,userData,dataLen );
    //////////////////////////  填充用户数据  /////////////////////////////////
    uint8_t* puserData = buf + sizeof( struct iphdr) + sizeof( struct udphdr );
    memcpy( puserData, userData, dataLen );
}

/**
 * @brief GetTotalMegBuf
 * @param dataLen [in] 要发送的用户层数据长度
 * @param buf [in] 要指向内存的指针的引用
 * @return 开辟的buf 的长度
 */
uint16_t GetTotalMegBuf( size_t dataLen , uint8_t*& buf )
{
    if ( nullptr != buf )
    {
        throw std::invalid_argument( "buf is a uninitialed buffer");
    }
    //开辟总内存空间
    //因为 ip 头没有增加其他 option 所以总长度=两个包头长度加上数据长度
    int totalLen = sizeof( struct ip ) + sizeof( struct udphdr ) + dataLen;
    //开辟内存空间
    buf = new uint8_t[ totalLen ];
    return totalLen;
}

/**
 * @brief ReleaseBuf
 * @param buf 要释放的内存地址
 */
void ReleaseBuf( uint8_t*& buf )
{
    if ( nullptr != buf )
    {
        delete buf;
        buf = nullptr;
    }
}




void UdpAttack( const std::string& fakeIp,uint16_t fakePort,
                                    const std::string& dstIp, uint16_t dstPort,const void* data, uint16_t dataLen )
{
        //网络层设置为原始，传输层设置为 udp
    int sockfd = socket(AF_INET,SOCK_RAW,IPPROTO_UDP );
    if ( sockfd < 0 )
    {
        throw( RawSocketError( strerror( errno ) ) );
    }

    //开启 IP_HDRINCL 特性, 我们完全手动构造 ip 报文。
    const int on =1;
    if (setsockopt (sockfd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on)) < 0)
    {
        throw( RawSocketError( strerror( errno ) ) );
    }
    //获取一个 buf 用于存放 网络层，传输层，用户层数据。
    uint8_t *buf = nullptr;
    uint16_t totalLen = GetTotalMegBuf( dataLen, buf );
    //填充网络层数据
    ConstructFakeIpLayer( fakeIp,dstIp,buf, totalLen );
    //填充传输层数据
    ConstructFakeUdpLayer( (struct iphdr*)buf,fakePort,dstPort,data,dataLen,buf,totalLen );
    //填充目标地址信息
    struct sockaddr_in	target;
    bzero( &target, sizeof( struct sockaddr_in ) );
    target.sin_family = AF_INET;
    target.sin_port = htons( dstPort );
    if ( !inet_aton( dstIp.c_str(),&target.sin_addr ) )
    {
        throw std::invalid_argument( strerror( errno ) );
    }
    //发送数据
    sendto( sockfd, buf, totalLen, 0, (struct sockaddr*)&target,sizeof( struct sockaddr_in) );
    //释放地址
    ReleaseBuf( buf );
}




int main( int argc, char** argv )
{
    if ( argc != 2 )
    {
        puts( "Params is error!" );
        return -1;
    }

    std::string radiusIp( argv[1] );

    AAAPack pack;
    std::string name = "yuhaiyang";
    std::string pass = "123";
    std::string nasIdentifider = "172.21.2.187";

    pack.setCode( EMsgCode::ACCESS_REQUEST );
    pack.setIdentifier( 123 );
    pack.setScret( "ct10000" );
    std::array<uint8_t,16> arry = { 0,1,2,3,4,5,6,7,8,9 };
    pack.setAuthenticator( arry );
    //增加属性
    pack.addAttr( EAttrType::USER_NAME,name.c_str(), name.length() );
    //pack.addAttr( EAttrType::USER_PASS, pass.c_str(), pass.length() );
    pack.addAttr( EAttrType::NAS_IDENTIFIER, nasIdentifider.c_str(), nasIdentifider.length());


    uint8_t* pmsg;
    uint8_t dataLen;
    pack.pack( pmsg, dataLen );

    UdpAttack( "172.21.2.187",8089, radiusIp,1812, pmsg, dataLen );
    return 0;
}
